Rules for Tools/Loading and Saving
Loading and Saving as part of an Environment
As you may have noticed, there are five additional routines to which the Accessory
structure may optionally point: `load()', `save()', `size()', `install()', and
`clear()'. These provide a mechanism for attaching Accessory specific data to songs in
Bars&Pipes. The first three are used to load and save Accessory data on a song by song
basis. The last two are used to install that data once loaded and remove it if no
longer needed. These routines are useful if your Accessory associates a set of data
with each song. For instance, a patch librarian might save a buffer of system
exclusive sound data with each song.
Bars&Pipes keeps track of multiple songs with the Environment structure (see
`bars.h'.) The Environment structure is actually an exact duplicate of the top half of
the `Functions' structure. It contains the same set of global data that define a song.
As a result, each song can have all of its Tracks, Tools, Events, etc., accessed
through one Environment. To install an Environment, Bars&Pipes transfers all of the
Environment data to the Functions structure.
The `load()', `save()', `size()', `install()', and `clear()' commands all use an
Environment structure as a reference, which allows Bars&Pipes to instruct your
Accessory to load data for an Environment that is not currently active. Because your
Accessory's data is not defined as part of the standard Environment structure, you must
keep track of it internally, and use the Environment pointer as a reference to indicate
which set of data Bars&Pipes is asking you to act on when it calls your routines.
For example, we have a patch librarian Accessory that keeps a system exclusive
buffer of 100 bytes for each song. The buffer data structure looks like this:
struct Sysex {
struct Sysex *next; /* Maintain a list. */
struct Environment *song; /* Song this belongs to. */
char buffer[100]; /* Data. */
};
The Accessory keeps a linked list of these buffers, one for each song it has loaded.
When one of its five routines is called by Bars&Pipes, it finds the appropriate Sysex
structure by comparing the Environment pointer in the routine call with the song
pointer in each structure.
To best explain how each of the routines should work, here's the code for each:
/* First, define the Sysex list and provide a routine to
scan it for an Environment match. */
struct Sysex *sysexlist = 0;
struct Sysex * scansysex(environment) struct Environment *environment; {
struct Sysex *sysex = sysexlist;
for (;sysex;sysex = sysex->next) {
if (sysex->song == environment) break;
}
return(sysex);
}
/* A routine to allocate a new Sysex structure and
place it in the list. */
struct Sysex * allocsysex(environment)
struct Environment *environment;
{
struct Sysex *sysex;
sysex = (struct Sysex *) (*functions->myalloc)(sizeof(struct Sysex),0);
if (sysex) {
sysex->song = environment;
sysex->next = sysexlist;
sysexlist = sysex;
}
return(sysex);
}
/* This is the load routine. Find or create the sysex
structure that matches the environment pointer,
then read into it. Also, if the environment pointer
is functions itself, it is the current active environment.
Go ahead and install it and since this is a patch
librarian, send the sysex buffer out the
serial port using the mythical routine sendsysex() . */
load(file,environment,size)
long file; /* Fast File io pointer. */
struct Environment*environment; /* Environment. */
long size; /* Size of segment to read. */
{
char buff[100];
struct Sysex *sysex;
geta4();
sysex = scansysex(environment);
if (!sysex) sysex = allocsysex(environment);
if (sysex) {
(*functions->fastread)(file,sysex->buffer,size);
if (environment == functions)
sendsysex(sysex->buffer,size);
}
else (*functions->fastread)(file,buff,size);
return(1);
}
/* Saving requires two routines: size() and save(). Size()
just returns the size of the buffer it needs to save,
while save() actually saves the data. */
size(environment)
struct Environment *environment;
{
geta4();
if (scansysex(environment)) return(100);
else return(0);
}
/* To save, the format is always a four byte identifier
(the id of this Accessory) followed by a four byte
size, followed by the data. Return 1 if failed, 0
if successful. */
save(file,environment)
long file;
struct Environment *environment;
{
struct Sysex *sysex;
long size;
long id;
geta4();
sysex = scansysex(environment);
size = size(environment);
id = ID_THISACCESSORY;
if ((*functions->fastwrite)(file,&id,4) == -1) return(1);
if ((*functions->fastwrite)(file,&size,4) == -1)
return(1);
if (sysex) {
if ((*functions->fastwrite)
(file,sysex->buffer,size) == -1) return(1);
}
return(0);
}
/* To install an environment, find the Sysex structure
that corresponds with it and send it out. Also,
change the environment pointer to be functions, since
it is now installed. */
install(environment)
struct Environment *environment; /* Environment. */
{
struct Sysex *sysex;
geta4();
sysex = scansysex(environment);
if (sysex) {
sendsysex(sysex->buffer,100);
sysex->song = (struct Environment *) functions;
}
return(1);
}
/* To clear an environment, remove it from the linked list
and deallocate the memory. */
clear(environment)
struct Environment *environment; /* Environment. */
{
struct Sysex *sysex, *remove;
geta4();
sysex = sysexlist;
if (sysex->song == environment) {
sysexlist = sysex->next;
(*functions->myfree)(sysex,sizeof(struct Sysex));
return(1);
}
else {
for (;sysex->next;sysex = sysex->next) {
if (sysex->next->song == environment) {
remove = sysex->next;
sysex->next = sysex->next->next;
(*functions->myfree)
(remove,sizeof(struct Sysex));
return(1);
}
}
}
return(0);
}
To compile your accessory, follow the same guidelines for compiling a Tool,
including linking with `toolstart.o'.
The entire source code to MuFFy, the MIDI File Format converter, is included on the
Rules For Tools disk. It is commented, so feel free to browse through it and figure
out how it works. You might be tempted to write your own file converter to translate
Bars&Pipes compositions into yet another format.